#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2025-2026 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#使用方法
##  python api_checker/api_checker.py --sdk=xxxx --includePaths="xx,xx"
## python api_checker/api_checker.py --sdk=/srv/code/out/sdk/ohos-sdk/linux，其中linux包含ets/ets1.2目录
## python api_checker/api_checker.py --sdk=$PWD/out/sdk/ohos-sdk/linux，其中linux包含ets/ets1.2目录

import platform
import subprocess
import os
import tempfile
import json
import argparse
from collections import defaultdict
import shutil
import sys
import time
import threading

parser = argparse.ArgumentParser(description='manual to this script')
parser.add_argument("--sdk", type=str, default="")
parser.add_argument("--scan", type=str, default="")
parser.add_argument("--es2pandaPath", type=str)
parser.add_argument("--includePaths", type=str)
args = parser.parse_args()


global_ohSdk = '' # sdk路径
global_scanPath= '' # 扫描路径
sep = os.sep # 系统分隔符

temp_arktsconfig_path = '' # arktsconfig.json文件路径

excludedFileList = [] # 忽略的声明文件
interfaceFileList = [] # 收集要处理的声明文件
chunkSize = 100 # 并发执行分块数量
# 创建锁对象用于文件写入同步
file_lock = threading.Lock()
logPrintList = [] # 文件日志输出列表

total_count = 0
timeout = 0
compile_error = 0
compile_warning = 0
compile_pass = 0
compile_dump = 0
pass_list = []


if args.sdk != "" and args.scan != "":
        global_ohSdk = args.sdk
        global_scanPath = args.scan
elif args.sdk != "" and args.scan == "":
        global_ohSdk = args.sdk
        global_scanPath = args.sdk+'/ets/ets1.2/api'
        if not os.path.exists(global_scanPath):
            global_scanPath = global_scanPath.replace('ets1.2', 'static')
        print('global_scanPath=' + global_scanPath)


global_es2pandaPath = args.es2pandaPath if args.es2pandaPath else global_ohSdk + '/ets/ets1.2/build-tools/ets2panda' # es2panda路径
if not os.path.exists(global_es2pandaPath):
    global_es2pandaPath = global_es2pandaPath.replace('ets1.2', 'static')
print('global_es2pandaPath=' + global_es2pandaPath)

global_includePaths = [item.strip() for item in args.includePaths.split(',')] if args.includePaths else []
if platform.system() == 'Windows':
    excutefile = global_es2pandaPath + '/bin/es2panda.exe'
else:
    excutefile = global_es2pandaPath + '/bin/es2panda'

# 扫描此四个文件夹下的文件
def scanFun(paths_dict):
    scan_path_list = [f'{sep}api{sep}',f'{sep}arkts{sep}',f'{sep}component{sep}',f'{sep}kits{sep}']

    _scan_path = os.path.join(global_ohSdk, "ets", "ets1.2")
    if not os.path.exists(_scan_path):
        _scan_path = os.path.join(global_ohSdk, "ets", "static")
    print('_scan_path=' + _scan_path)
    
    for root, dirs, files in os.walk(_scan_path, followlinks=True):
        absPath = os.path.abspath(root)
        if root.find('build-tools') > 0:
            continue
        for file in files:
            # 只扫描.ets文件
            if 'd.ets' in file:
                # 拼凑获取绝对路径
                file_path = os.path.join(absPath, file)
                for scan_path in scan_path_list:
                    if scan_path in file_path:
                        key = ''
                        val = file_path.split('.d.')[0]
                        path_list = val.split(scan_path)[1].split(sep)
                        # print(path_list)
                        if not path_list[-1].startswith('@'):
                            path_list = val.split(scan_path)[1].split(sep)
                            key = '.'.join(path_list)
                        else:
                            key = file.split('.d.')[0]
                        # print(key)
                        paths_dict[key] = [val]
                        # 如果匹配到scan_path_list中任意一个路径，那么直接break，提升性能
                        break
    return paths_dict

# 使用目录下面的情况
def createArktsconfig():
    if not os.path.exists(f'{global_es2pandaPath}/bin/arktsconfig.json'):
        print("not found arktsconfig.json")
        return None
    configJsonF = open(f'{global_es2pandaPath}/bin/arktsconfig.json', 'r+')
    configDict = json.load(configJsonF)
    configJsonF.close()
    configDict["compilerOptions"]["package"] = "entry"
    currentDict = configDict["compilerOptions"]
    currentDict  = configDict["compilerOptions"]["paths"]
    
    # 修改相对路径为绝对路径
    for key in currentDict:
        if '../' in currentDict[key][0]:
            currentDict[key][0] = f"{global_es2pandaPath}/bin/{currentDict[key][0]}"

    # 扫描方法
    paths_dict = scanFun({})
    # 将扫描后的dict拼起来
    currentDict.update(paths_dict)
    # 创建一个临时文件，并将内存中的数据写到临时文件
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_file:
        data = configDict
        # 读取临时文件
        json.dump(data, temp_file)
        temp_file_path = temp_file.name
    return temp_file_path


def process_files(file_list, thread_id, abc_folder_path):
    # in api folder
    global total_count,compile_error,compile_warning,compile_pass,timeout,compile_dump,pass_list
    global temp_arktsconfig_path
    for file_path in file_list:
        total_count += 1
        try:
            file_name = os.path.basename(file_path)
            command = [excutefile, '--extension', 'ets','--arktsconfig', temp_arktsconfig_path, '--ets-module', file_path, "--output", abc_folder_path+'/'+file_name+'.abc']
            print('')
            print(' '.join(command))
            result = subprocess.run(command, capture_output=True, text=True, timeout=120)
            if len(result.stdout) == 0:
                compile_pass += 1
                pass_list.append(file_path)
            else:                        
                lst = result.stdout.split('\n')
                for info in lst:
                    if (len(info) == 0):
                        continue
                    if 'Warning' in info:
                        compile_warning += 1
                        continue
                    if 'Structs are only used to define UI components' in info:
                        continue
                    print(f"{file_path} -------- {info}")
                    logPrintList.append(' '.join(command) + '\n')
                    logPrintList.append(f"{file_path} ----- {info}" + '\n')
                    file_name = os.path.basename(file_path)
                    if file_name in excludedFileList:
                        print('skip file=' + file_name)
                        continue
                    compile_error += 1
            if len(result.stderr) != 0:
                compile_dump += 1
        except subprocess.TimeoutExpired as e:
            timeout += 1


# 扫描路径下面的ets
def checksdk():
    global temp_arktsconfig_path
    # 指定临时编译abc文件的路径
    abc_folder_path = './tempabc'
    if os.path.exists(abc_folder_path):
        if os.path.isdir:
            # 使用shutil.rmtree()函数删除文件夹及其所有内容
            shutil.rmtree(abc_folder_path)
        else:
            os.remove(abc_folder_path)
    # 使用os.makedirs()方法创建文件夹，如果文件夹已存在会抛出异常
    os.mkdir(abc_folder_path)
    os.makedirs('./out/sdk/', exist_ok=True)

    temp_arktsconfig_path = createArktsconfig()
    if not temp_arktsconfig_path:
        return 
    
    # 首先计算需要处理的文件总数
    total_files = 0
    for root, dirs, files in os.walk(global_scanPath, followlinks=True):
        for file in files:
            file_path = os.path.join(root, file)
            if '.d.ets' not in file_path and '.d.ts' not in file_path:
                continue
            if len(global_includePaths) > 0 and file not in global_includePaths:
                continue
            # if file in excludedFileList:
            #     print('skip file=' + file)
            #     continue
            total_files += 1
            interfaceFileList.append(file_path)
    print('total_files=' + str(total_files))
    
    sub_lists = [interfaceFileList[i:i+chunkSize] for i in range(0, len(interfaceFileList), chunkSize)]
    # 创建并启动线程
    threads = []
    for i, sub_list in enumerate(sub_lists):
        thread = threading.Thread(target=process_files, args=(sub_list, i, abc_folder_path))
        threads.append(thread)
        thread.start()
    # 等待所有线程完成
    for thread in threads:
        thread.join()
    
    with open('./out/sdk/api_checker.log', 'a', encoding='utf-8') as log_print:
        for line in logPrintList:
            log_print.write(line)    

    print("total_count:", total_count, "\ntimeout:", timeout, "\ncompile_error:", compile_error, "\ncompile_warning:", compile_warning,
          "\ncompiler_dump:", compile_dump, "\ncompile_pass:", compile_pass)
    # 删除临时文件
    # os.remove(temp_arktsconfig_path)
    # 使用shutil.rmtree()函数删除文件夹及其所有内容
    shutil.rmtree(abc_folder_path)


if __name__ == "__main__":
    time.sleep(5)
    start = time.perf_counter()
    # ALL  Structs are only used to define UI components
    excludedFileList.append('@ohos.multimedia.avInputCastPicker.d.ets')
    excludedFileList.append('@ohos.arkui.advanced.SegmentButton.d.ets')
    # 0702
    # excludedFileList.append('@ohos.distributedHardware.mechanicManager.d.ets')
    # master
    # excludedFileList.append('systemTonePlayer.d.ets')
    # excludedFileList.append('@ohos.multimedia.systemSoundManager.d.ets')
    # excludedFileList.append('@ohos.web.WebNativeMessagingExtensionAbility.d.ets')
    # excludedFileList.append('@ohos.screenshot.d.ets')
    # excludedFileList.append('@ohos.contact.d.ets')
    # excludedFileList.append('@ohos.arkui.ArcList.d.ets')
    # excludedFileList.append('@ohos.multimedia.avCastPicker.d.ets')
    # excludedFileList.append('@ohos.arkui.advanced.ExceptionPrompt.d.ets')
    # excludedFileList.append('@ohos.web.WebNativeMessagingExtensionContext.d.ets')

    chunkSize = 50
    checksdk()
    end = time.perf_counter()
    elapsed = end - start
    print(f"duration: {elapsed:.6f} 秒")
    # 如果编译错误大于0，则退出程序
    if compile_error > 0:
        sys.exit(1) 
